package com.sky.aspect;

import com.sky.annotation.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MemberSignature;
import org.springframework.stereotype.Component;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.reflect.Method;
import java.time.LocalDateTime;

/**
 * 自定义切面，实现公共段自动填充功能，统一拦截加入了AutoFill注解的方法进行公共字段填充
 */
@Aspect// 表示当前类是一个切面类
@Component// 将当前组件注册到Spring容器中
@Slf4j// 记录日志
public class AutoFillAspect {
    //切面==切点+通知
    /**
     * 切入点 ： 对哪个方法进行拦截
     */
    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")// *表示任意修饰符和任意返回值
    // @annotation(com.sky.annotation.AutoFill)表示当前方法被AutoFill注解所标注
    public void autoFillPointCut(){}

    /**
     * 前置通知：在目标方法开始之前执行，用于在通知中进行公共字段的赋值
     */
    @Before("autoFillPointCut()")// 表示一个前置通知，里头指定一个切入点，当匹配到切点表达式autoFillPointCut()方法时，会执行此方法
    public void autoFill(JoinPoint joinPoint)  {//参数是连接点
        log.info("开始进行公共字段自动填充...");

        //获取到当前被拦截的方法上的数据库操作类型
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();//获取方法签名对象
        AutoFill autoFill =signature.getMethod().getAnnotation(AutoFill.class);//获取方法上的注解对象，这样可以获取到注解中配置的参数信息
        //
//        MethodSignature接口没有getAnnotation()方法
//        只有Method类才有getAnnotation()方法来获取方法上的注解
//                总结
//        getMethod()不能省略，因为：
//        需要通过它获取实际的Method对象
//        只有Method对象才能调用getAnnotation()方法获取方法上的注解
//                这是Java反射API的标准用法
        //signature.getMethod() 获取被拦截方法的 Method 对象，
        // 注解（Annotation） 是一个类型，就像类或接口一样，它只是一个模板或定义
        //注解对象（Annotation Object） 是注解在具体方法上的实例，包含了实际的配置值
        //注解类只是一个模板，不包含运行时的具体配置，你无法获取到具体方法上配置的 OperationType 值，因为：
        //不同方法可能使用相同的注解类型但配置不同的参数值
        OperationType operationType = autoFill.value();//获取数据库操作类型

        //获取到当前被拦截的方法的参数---实体对象,因为要为他的属性赋值
        Object[] args = joinPoint.getArgs();//获取所有方法参数
        if(args == null || args.length == 0){
            return;
        }
        Object entity = args[0];//获取当前被拦截的方法的参数，即实体对象,所有对象父类为 Object,用这个接收是因为对象类型无法判断。

        //准备赋值的数据
        LocalDateTime now = LocalDateTime.now();//获取当前时间
        Long currentId = BaseContext.getCurrentId();//获取当前登录用户的ID

        //根据当前不同的操作类型，为对应的属性通过反射赋值
        if(operationType==OperationType.INSERT){
            //为四个公共字段赋值
            try {
                Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
                //在编译时，编译器只知道entity是Object类型，而Object类中没有setCreateTime方法，需要获取实际的类信息才能拿到方法
                //getClass()不能省略，因为：表示获取对象的实际运行时类型，entity.getClass()返回的就是类对象
                //我们需要获取对象的实际类型信息
                //编译器只认识Object类型的方法，不认识具体实体类的方法
                //反射机制允许我们在运行时动态调用对象的方法，实现通用处理
                //这就是为什么必须使用entity.getClass().getDeclaredMethod()而不是直接调用entity.setCreateTime()的原因。
                Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
                //获取方法对象的具体步骤
                //1. entity.getClass()
                //获取entity对象的运行时类型（类对象）
                //例如：如果entity是Employee对象，则返回Employee.class
                //2. getDeclaredMethod()方法
                //这是Class类的一个方法，用于获取类中声明的方法
                //参数说明：
                //第一个参数：方法名（字符串）
                //后续参数：方法参数类型列表
                //3. AutoFillConstant.SET_CREATE_USER
                //这是一个常量字符串，根据命名推测值为"setCreateUser"
                //定义在AutoFillConstant类中
                //4. Long.class
                //指定方法参数的类型
                //表示要查找的方法接受一个Long类型的参数



                //通过反射为对象属性赋值
                setCreateTime.invoke(entity,now);
                setUpdateTime.invoke(entity,now);
                setCreateUser.invoke(entity,currentId);
                setUpdateUser.invoke(entity,currentId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }else if(operationType==OperationType.UPDATE){
            //为2个公共字段赋值
            try {
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
                //通过反射为对象属性赋值
                setUpdateTime.invoke(entity,now);
                setUpdateUser.invoke(entity,currentId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }
}
